package com.lai.laiojcodesandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DockerClientBuilder;
import com.lai.laiojcodesandbox.enums.LanguageEnums;
import com.lai.laiojcodesandbox.model.ExecuteCodeRequest;
import com.lai.laiojcodesandbox.model.ExecuteCodeResponse;
import com.lai.laiojcodesandbox.model.ExecuteMessage;
import com.lai.laiojcodesandbox.model.JudgeInfo;
import com.lai.laiojcodesandbox.pool.DockerContainerPool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;


/**
 * 代码沙箱 模版方法 设计模式
 */
@Slf4j
@Component
public abstract class CodeSandboxTemplate implements CodeSandbox {


    // 容器池队列
    @Resource
    private DockerContainerPool dockerContainerPool;

    // 代码存放目录
    private static final String GLOBAL_CODE_DIR_NAME = "/home/uu/桌面/laioj-codeSandbox/tmpCode";

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        log.error("执行中获取到的容器池：{}", dockerContainerPool.toString());
        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
        // 获取输入值
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        String language = executeCodeRequest.getLanguage();

        // A. 从容器池获取容器Id并取出
        String containerId = dockerContainerPool.acquireContainer();

        // 没有空闲的容器
        if (containerId == null) {
            return null;
        }

        log.error("获取到的容器Id为：{}", containerId);

        try{
            // 1. 保存代码文件
            String dirName = UUID.randomUUID().toString();
            File userCodeFile = saveCodeFile(code, language, dirName);

            // 2. 编译代码，获取 class 文件
            if (LanguageEnums.JAVA_INFO.getLanguage().equals(language)) {
                ExecuteMessage executeMessage = compileFile(userCodeFile);
                System.out.println(executeMessage);
            }

            // 3. 执行代码，获取输出结果
            List<ExecuteMessage> executeMessageList = runFile(userCodeFile, inputList, dockerClient, containerId, dirName);
            log.error("执行代码后的原始数据:{}", executeMessageList);

            // 4. 收集整理输出结果
            ExecuteCodeResponse outputResponse = getOutputResponse(executeMessageList);

            // 5. 文件清理
//            Boolean b = deleteFile(userCodeFile);
//            if (!b) {
//                log.error("文件删除失败：{}", userCodeFile.getAbsolutePath());
//            }

            //return
            return outputResponse;
        }finally {
            // 容器用完放入容器池中
            dockerContainerPool.releaseContainer(containerId);
            log.error("执行完成后容器池所剩容器：{}", dockerContainerPool);
        }

    }

    /**
     * 1. 保存代码文件
     *
     * @param code
     * @return
     */
    public File saveCodeFile(String code, String language, String dirName) {

        // 判断临时代码目录是否存在，没有则创建
        if (!FileUtil.exist(GLOBAL_CODE_DIR_NAME)) {
            FileUtil.mkdir(GLOBAL_CODE_DIR_NAME);
        }
        // 把用户的代码隔离存放
        String userCodeParentPath = GLOBAL_CODE_DIR_NAME + File.separator + dirName;
        // 创建用户代码的父目录，如果不存在
        if (!FileUtil.exist(userCodeParentPath)) {
            FileUtil.mkdir(userCodeParentPath);  // 创建父目录
        }
        log.error("创建目录：{}", userCodeParentPath);

        String userCodePath = getUserCodePath(language, userCodeParentPath);

        System.out.println("保存的代码："+code);

        // 设置编码格式并写入文件
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
        System.out.println("保存文件成功：" + userCodePath);
        return userCodeFile;


    }

    /**
     * 设置 保存文件 名称及后缀名
     *
     * @param language           语言
     * @param userCodeParentPath 父目录
     * @return 保存文件路径/名称
     */
    private String getUserCodePath(String language, String userCodeParentPath) {
        String userCodePath = userCodeParentPath + File.separator;

        if (LanguageEnums.JAVA_INFO.getLanguage().equals(language)) {
            userCodePath += LanguageEnums.JAVA_INFO.getClassName();
        } else if (LanguageEnums.PYTHON_INFO.getLanguage().equals(language)) {
            userCodePath += LanguageEnums.PYTHON_INFO.getClassName();
        } else if (LanguageEnums.C_INFO.getLanguage().equals(language)) {
            userCodePath += LanguageEnums.C_INFO.getClassName();
        } else {
            userCodePath += LanguageEnums.DEFAULT_INFO.getClassName();
        }
        return userCodePath;
    }

    /**
     * 2. 编译代码，获取 class 文件
     *
     * @param userCodeFile
     * @return
     */
    public ExecuteMessage compileFile(File userCodeFile) {
        return null;
    }

    /**
     * 3. 执行代码，获取输出结果
     *
     * @param useCodeFile
     * @param inputList
     * @return
     */
    public List<ExecuteMessage> runFile(File useCodeFile
            , List<String> inputList, DockerClient dockerClient, String containerId, String dirName) {
        return null;
    }


    /**
     * 4. 收集整理输出结果
     *
     * @param executeMessageList
     * @return
     */
    public ExecuteCodeResponse getOutputResponse(List<ExecuteMessage> executeMessageList) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        List<String> outputList = new ArrayList<>();
        long maxTime = 0;
        long maxMemory = 0;
        for (ExecuteMessage executeMessage : executeMessageList) {
            if (StrUtil.isNotBlank(executeMessage.getErrorMessage())) {
                executeCodeResponse.setMessage(executeMessage.getErrorMessage());
                // 用户提交的代码执行中存在错误
                executeCodeResponse.setStatus(3);
                break;
            }
            outputList.add(executeMessage.getMessage());

            Long time = executeMessage.getTime();
            if (time != null) {
                maxTime = Math.max(maxTime, time);
            }
            Long memory = executeMessage.getMemory();
            if (memory != null) {
                maxMemory = Math.max(maxMemory, memory);
            }
        }
        // 正常运行完成
        if (outputList.size() == executeMessageList.size()) {
            executeCodeResponse.setStatus(1);
        }

        executeCodeResponse.setOutputList(outputList);
        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(maxTime);
        judgeInfo.setMemory(maxMemory);
        executeCodeResponse.setJudgeInfo(judgeInfo);

        return executeCodeResponse;
    }

    /**
     * 5. 文件清理
     *
     * @param userCodeFile
     */
    public Boolean deleteFile(File userCodeFile) {
        // 判断文件的父目录是否存在，有可能程序在执行过程由于服务器空间不足被强制删除
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
        if (userCodeFile.getParentFile() != null) {
            boolean del = FileUtil.del(userCodeParentPath);
            log.info("删除" + (del ? "成功" : "失败"));
            return del;
        }
        return true;
    }

    /**
     * 获取错误响应（代码沙箱错误
     *
     * @param throwable
     * @return
     */
    private ExecuteCodeResponse getErrorResponse(Throwable throwable) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(throwable.getMessage());
        // 表示代码沙箱错误
        executeCodeResponse.setStatus(2);
        executeCodeResponse.setJudgeInfo(new JudgeInfo());
        return executeCodeResponse;
    }


}
